详解主流Java应用服务器的工作原理及组件设计(有彩蛋)
本文根据DBAplus社群第138期线上分享整理而成,文末还有好书送哦~
刘光瑞
《Tomcat架构解析》作者
现任窝客研发总监,负责窝客产品研发管理及总体架构设计。
拥有十几年企业级大型业务系统研发架构经验,成功带领团队设计并研发基于Tomcat的组件化微服务架构。
热衷于系统架构、Java技术栈及应用服务器中间件的学习及研究。
今天所讲内容的大纲如下:
Java应用服务器分类
Servlet容器工作原理
Tomcat组件及请求处理
大概从2000年以后,Java应用服务器的使用主要经历了3个阶段:J2EE应用服务器、Servlet容器及Web服务器、响应式微服务。
1、J2EE应用服务器
首先,是J2EE应用服务器。这部分在以Spring为主的without EJB的应用框架出现之前,基本上是企业应用开发的标配。这类应用服务器严格实现了J2EE规范,通过了SUN的相关认证,还支持标准的基于J2EE的应用开发,包括EJB、JMS、JTA、Servlet、JSF等等。
这类应用服务器有很强的配置和优化能力,非常适合集中式的大型企业级应用开发。这一类的产品情况如下:
像WebSphere这一类商用产品,是大型的银行、电信应用的主流服务器,甚至配合IBM的JVM实现,有着非常好的性能优势。
免费的诸如JBOSS,使用也非常广泛,作为商用企业级应用服务器的减配版。
企业级应用服务器的特点就是过重。大概2010年之后(具体时间不确定),有几个转向了OSGi这种模块化架构,期望提供一种更加灵活的架构。像JBoss这一类产品在互联网规模化、轻量化的大背景下,似乎使用的相对越来越少。但是在集中式的企业级应用场景,它们应该还是主流,轻量级服务器是很难取代的。
2、Servlet容器及Web服务器
这一类在后面才提到,并不是说它们的历史较短,像Tomcat的历史可以说是最长的几款服务器实现。而是说它们被大规模用于生产环境的尝试,相对较晚。
这是与互联网应用大规模分布式架构分不开的。在轻量级的架构中,绝大多数应用实际上主要使用了服务器的Servlet容器部分,其它部分很少使用,而是使用更完善的第三方框架去替代。这时候,J2EE服务器显得过重,所以开始更倾向于只使用Servlet容器提供HTTP服务。
这一类服务器见图片,主要是Tomcat、Resin、Jetty、Undertow。
Tomcat大家想必都很熟悉了,基本Java Web应用开发入门就使用。它主要的使用场景还是独立启动部署Web应用,嵌入式的场景与Jetty相比,要相对差一些;Undertow出现相对晚一些,它是JBoss实现的新的Servlet容器,以前JBoss直接使用Tomcat作为Servlet容器。
如果大家基于Spring Boot开发应用,想必都知道,现在Spring Boot对这三款容器都支持,现在用的比较多的是Tomcat和Jetty,Undertow使用的相对较少。
后两者是很少作为独立启动的服务器来使用的,尤其是Undertow,应该不支持这种场景。
3、响应式微服务
这一类严格说并不是Web服务器。有时候(如微服务架构),我们更倾向于直接提供HTTP服务,这个时候并不需要支持Web,不需要Servlet规范。
这一类就是为这种场景设计的,目前有两类方案可以来满足这种场景,一种是Vert.x,这个国内目前使用应该不是很多。主要是它不支持Servlet规范,这估计是一方面的原因。
还有一种是Play Framework内置的基于事件的HTTP服务,和Vert.x类似,它们都是为高并发的互联网服务开发实现的。今天对于这种方案我们不做展开,还是以主流的Servlet方案为主。
应用服务器基于Servlet提供基础的HTTP服务,包括Web服务、SOA,即便是JSP页面,最终也是通过Servlet实现的。因此Servlet容器可以说是Web服务的核心。
1、容器的核心部分
对于一款Servlet容器,主要分为两部分:链接器和容器。前者是服务器对I/O的封装,后者是对Servlet规范的实现。
大家可以简要看一下这一篇的内容:一款服务器通过链接器读取网络请求,将其转换为符合Servlet规范的Request对象,同时负责将Servlet容器的响应输出到客户端。
它对Servlet容器屏蔽了协议及I/O方式等的区别。无论是HTTP还是AJP、还是HTTPS,在容器中获取到的都是一个标准的Request对象。
而容器呢,主要负责三个方面的工作:
按照Servlet规范(Web.xml/注解等等),识别部署的Web应用,将其解析为内部的请求处理组件;
接收链接器的请求调用(一般不需要容器负责请求映射,映射部分是链接器完成的);
按照Servlet规范进行请求处理,这部分主要是Filter等的处理。
2、工作原理
大家可以看一下这个示意图:
对于链接器,无论采用哪一种I/O,都是采用一种线程池的方式来处理的。
主要有两个线程池,一个用于接收请求,一个用于处理请求。接收请求的线程负责读取请求头,完成请求映射,并将请求提交到映射到的请求处理组件(一个指定的Servlet)。
这儿大家注意一点,容器映射完成的结果是一个Servlet,而不是一个Web应用,至少Tomcat是这样处理的,Jetty更轻量。
虽然Servlet的具体实现不同,但是完成的工作基本类似,而且模式也类似(除去我们说的第三类)。
接下来我们具体看一下Tomcat是如何实现Servlet容器的:相对于Jetty轻量的处理架构,Tomcat更像“服务器”一些,它考虑了一些服务器的特性,如虚拟主机。
1、容器主要组件
Tomcat的主要组件可以看一下下面这张图:它底层通过Java命名服务统一对各种组件进行管理,体现了服务器软件的“管理”特性;然后是链接器实现Coyote和Jasper JSP引擎。
Tomcat与Jetty相比,它更是一款完整的Servlet容器方案。而Jetty是以模块化的形式提供了Servlet容器以及各种HTTP工具集。像JSP引擎,Jetty直接使用的Tomcat中的Jasper(还有另一种可选方案)。
最上面的Catalina是Tomcat容器的实现。大家如果看Tomcat的异常堆栈,就会发现包名基本都是以这个作为开头。
Tomcat链接器,我们可以简单的按两层进行划分:应用层和传输层。
应用层里,最新版本的Tomcat支持HTTP、AJP以及HTTP2,传输层支持NIO、NIO2以及APR,最新版本已经不支持BIO。
链接器还有一个很重要的组件就是线程池,想必做过基本的Tomcat优化的人都调整过Tomcat并发链接数。线程池的分类我们刚才也说过了,分为接收线程池和请求处理线程池。
2、Tomcat Servlet容器组件
Servlet容器的组件见图:简单来说,Tomcat通过一种分层的架构使得Servlet容器有很好的灵活性,而且通过生命周期管理接口,为这些各层的对象提供统一的生命周期管理。
分层如下:Engine-Host-Context-Wrapper。引擎是顶级,我们可以认为就是容器本身。Host是虚拟主机的概念,这样可以在Tomcat配置多个虚拟主机。Context就是我们的Web应用,Wrapper是Web应用中的Servlet,所以还是刚才说的,Tomcat中的请求映射是体现到Servlet上。
基于分层的组件,Tomcat还提供了完善的生命周期事件处理机制,Web应用的自动扫描启动、热部署、卸载等都是通过事件实现的。如果阅读Tomcat代码,需要重点关注下面的生命周期监听。
HostConfig主要用于扫描Web应用,自动根据Web应用目录创建Context。ContextConfig重点处理Servlet规范,生成Servlet、Filter等各种规范组件。最后,Tomcat自身也实现了一种链式的请求处理机制,这一点和规范中的Filter类似,这就是Valve。
在Tomcat中,Filter中可以做的事情,Valve可以做;Valve可以做的事情,Filter却不一定可以做,需要深入和服务器交互的工作,一般都是用Valve实现。
3、请求处理过程
这是一张Tomcat请求处理过程的时序图,大家看一下就会发现。Tomcat在这种分层的容器设计方案下,请求处理是很简单的,重点在前期的请求接收及I/O处理(Processor的各种实现)、Mapper的映射以及Wrapper中的规范处理。
4、Tomcat的优化
最后一点内容,我们再概要介绍一下Tomcat的优化。今天介绍的相对比较基础,大家感兴趣的还是需要去看相关的专门的书籍。
Tomcat优化主要但不限于以下四个方面:线程池、协议、JVM以及I/O,之所以说不限于,是因为应用服务器的优化,不仅要去优化其自身,诸如操作系统等也是要统一考虑的。
线程池就是我们刚才说的两类。接收线程池一般都是CPU内核数即可。当然如果服务器核数非常多,可以适当调小。这个线程池处理速度比请求处理线程池要快的多,因此要考虑这两者的匹配,原因PPT中都介绍了。
做请求处理线程池这个设置时,最好做一些基准测试,并没有什么精确的经验值。
协议层面。这个最常见的就是开启HTTP GZip压缩。如果配置了前置的Web服务器,那么静态文件可以考虑直接放到Web服务器上,然后可以修改HTTP链接器的I/O方式。新版本的Tomcat默认是NIO,可以改为APR,后者可以充分发挥本地调用的优势,与直接使用Apache基本是类似的。
当然对于前置了Web服务器的,可以采用AJP协议与Web服务器链接。Apache应该没问题,Nginx好像没有官方的AJP实现。第三方的就需要考虑健壮性和支持程度了。
然后是JVM,这部分估计最常见的就是调整JVM的堆内存。对于垃圾回收算法,也需要根据具体的应用场景进行调整。是吞吐量优先还是响应时间优先,需要使用多少线程去处理,这些都需要在具体的硬件配置下进行具体的测试才行。
今天的内容到此就结束了,主要内容还是非常概要,每一部分大家如果想深入了解,都需要去阅读相关书籍。协议、规范、JVM,这些都是必不可少的。
彩蛋来了
在本文微信订阅号(dbaplus)评论区留下足以引起共鸣的真知灼见,小编将在本文发布后的隔天中午12点选出留言内容最精彩的3位读者,送出以下好书一本~
新规说明:同一个月份里,已获赠者将不可重复拿书。
特别鸣谢图灵社区为活动供图书赞助。
近期热文
提前排雷!分布式缓存的25个优秀实践与线上案例
借鉴Codis实现的两种不停机分库分表迁移方案
从Elasticsearch集群及数据层架构,看分布式系统设计
DBA+工具:SQL自审自上线,摆脱人肉审核就在当下
关联与下钻:快速定位MySQL性能瓶颈的制胜手段
最新活动
2018 Gdevops全球敏捷运维峰会(成都站)
↓↓↓点这里了解更多报名详情